/**
 * 
 */
package com.demo2do.core.persistence.support;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.demo2do.core.utils.DateUtils;


/**
 * @author downpour
 *
 */
public class Filter {
    
    private Map<String, Object> parameters = new LinkedHashMap<String, Object>();
    
    private List<String> sentences = new ArrayList<String>();
    
    /**
     * The default construtor
     */
    public Filter() {
        
    }

    /**
     * add hql: sentence
     * 
     * @param sentence
     * @param parameterKey  the key used in sql like 'name = :key'
     * @param value
     * @return
     */
    public Filter addSentence(String sentence, String parameterKey, Object value) {

        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            this.sentences.add(" " + sentence + " ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }
    
    /**
     * add hql: key = :value
     * 
     * @param key
     * @param value
     */
    public Filter addEquals(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("eq", null, key);

            this.sentences.add(" " + key + " = :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }
    
    /**
     * add hql: alias.key = :value
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addEquals(String alias, String key, Object value) {
        
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
    
        return addEquals(key, value);
    }
    
    /**
     * add hql: key LIKE :likeValue, where likeValue = '%:value%'
     * 
     * @param key
     * @param value
     * @return
     */
    public Filter addLike(String key, Object value) {

        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("like", null, key);

            this.sentences.add(" " + key + " LIKE :" + parameterKey + " ");
            this.parameters.put(parameterKey, "%" + value.toString() + "%");
        }

        return this;
    }
    
    /**
     * add hql: alias.key LIKE :likeValue, where likeValue = '%:value%'
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addLike(String alias, String key, Object value) {

        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
    
        return addLike(key, value);
    }

    /**
     * Add hql: key >=:startDate AND key <=:endDate
     * 
     * @param key
     * @param startDate
     * @param endStart
     * @return
     */
    public Filter addDateBetween(String key, Date startDate, Date endDate) {
        if(startDate != null) {
            String parameterKey = this.generateParameterKey("StartD", null, key);
            this.sentences.add(" " + key + " >= :" + parameterKey + " ");
            this.parameters.put(parameterKey, startDate);
        }
        if(endDate != null) {
            String parameterKey = this.generateParameterKey("EndD", null, key);
            this.sentences.add(" " + key + " <= :" + parameterKey + " ");
            this.parameters.put(parameterKey, endDate);
        }
        return this;
    }

    /**
     * Add hql: alias.key >=:startDate AND alias.key <=:endDate
     * 
     * @param alias
     * @param key
     * @param startDate
     * @param endStart
     * @return
     */
    public Filter addDateBetween(String alias, String key, Date startDate, Date endDate) {
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addDateBetween(key, startDate, endDate);
    }

    /**
     * Add hql for key is datetime format in db: key >=:startDate AND key <=(:endDate+1),
     * StartDate and endDate are date format
     * 
     * @param key
     * @param startDate
     * @param endStart
     * @return
     */
    public Filter addDateTimeBetween(String key, Date startDate, Date endDate) {
        if(startDate != null) {
            String parameterKey = this.generateParameterKey("StartDT", null, key);
            this.sentences.add(" " + key + " >= :" + parameterKey + " ");
            this.parameters.put(parameterKey, startDate);
        }
        if(endDate != null) {
            String parameterKey = this.generateParameterKey("EndD", null, key);
            this.sentences.add(" " + key + " <= :" + parameterKey + " ");
            this.parameters.put(parameterKey, DateUtils.addDays(endDate, 1));
        }
        return this;
    }

    /**
     * Add hql for key is datetime format in db: alias.key >=:startDate AND alias.key <=(:endDate+1),
     * StartDate and endDate are date format
     * 
     * @param alias
     * @param key
     * @param startDate
     * @param endStart
     * @return
     */
    public Filter addDateTimeBetween(String alias, String key, Date startDate, Date endDate) {
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addDateTimeBetween(key, startDate, endDate);
    }
    
    /**
     * add hql: key > :value
     * 
     * @param key
     * @param value
     * @return
     */
    public Filter addGreaterThan(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("gt", null, key);

            this.sentences.add(" " + key + " > :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }

    /**
     * add hql: key > :value
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addGreaterThan(String alias, String key, Object value) {
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addGreaterThan(key, value);
    }
    
    /**
     * add hql: key >= :value
     * 
     * @param key
     * @param value
     * @return
     */
    public Filter addGreaterThanEqual(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("ge", null, key);

            this.sentences.add(" " + key + " >= :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }

    /**
     * add hql: alias.key >= :value
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addGreaterThanEqual(String alias, String key, Object value) {
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addGreaterThanEqual(key, value);
    }
    
    /**
     * add hql: key < :value
     * 
     * @param key
     * @param value
     * @return
     */
    public Filter addLessThan(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("lt", null, key);

            this.sentences.add(" " + key + " < :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }

    /**
     * add hql: alias.key < :value
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addLessThan(String alias, String key, Object value) {
        
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addLessThan(key, value);
    }
    
    /**
     * add hql: key <= :value
     * 
     * @param key
     * @param value
     * @return
     */
    public Filter addLessThanEqual(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("le", null, key);

            this.sentences.add(" " + key + " <= :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }
    

    /**
     * add hql alias.key <= :value
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addLessThanEqual(String alias, String key, Object value) {
        
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addLessThanEqual(key, value);
    }

    /**
     * add hql: bitand(key, :value) > 0
     * 
     * @param key
     * @param value
     * @return
     */
    public Filter addBitandEquals(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("baeq", null, key);

            this.sentences.add(" bitand(" + key + ", :" + parameterKey + ") > 0 ");
            this.parameters.put(parameterKey, value);
        }

        return this;
    }
    
    /**
     * add hql: bitand(alias.key, :value) > 0
     * 
     * @param alias
     * @param key
     * @param value
     * @return
     */
    public Filter addBitandEquals(String alias, String key, Object value) {
        
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addBitandEquals(key, value);
    }
    
    /**
     * 
     * @param value
     * @return
     */
    private boolean checkStatus(Object value) {
        
        if(value instanceof EntityStatus) {
            EntityStatus entityStatus = (EntityStatus) value;
            return !entityStatus.isEmpty();
        }
        
        return true;
    }
    
    /**
     * Generate parameter key
     * 
     * @param operation
     * @param key
     * @return
     */
    private String generateParameterKey(String operation, String alias, String key) {
        
        StringBuffer sb = new StringBuffer(operation);
        
        if(!StringUtils.isEmpty(alias)) {
            sb.append(alias.substring(0, 1).toUpperCase() + alias.substring(1));
        }
        
        String[] temp = StringUtils.split(key, ".");
        for (int i = 0; i < temp.length; i++) {
            sb.append(temp[i].substring(0, 1).toUpperCase() + temp[i].substring(1));
        }
        
        return sb.toString();
        
    }
    
    /**
     * 
     * @return
     */
    public String getWhereSentence() {
        
        if(sentences.isEmpty()) {
            return "";
        }
        
        return " WHERE" + StringUtils.join(sentences, "AND");
    }
    
    /**
     * 
     * @return
     */
    public String getClauseSentence() {
        
        if(sentences.isEmpty()) {
            return "";
        }
        
        return " AND" + StringUtils.join(this.sentences, "AND");
    }
    
    /**
     * @return the parameters
     */
    public Map<String, Object> getParameters() {
        return parameters;
    }
    
}
